package skewreduce.lib;

import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

// (\inf,v2)
// (v1,v2)
// (v1,\inf)
// (\inf,\inf)

public class Histogram implements Iterable<Histogram.Bucket> {
	private static final Logger LOG = LoggerFactory.getLogger(Histogram.class);
	
	final double minValue;
	final double maxValue;
	final double bucketWidth;
	final int[] buckets;
	Bucket[] indexes;
	
	public final class Bucket implements Comparable<Bucket> {
		final int i;
		
		Bucket(int idx) { i = idx; }
		
		public double getMin() { return minValue + bucketWidth * i; }
		public double getMax() { return minValue + bucketWidth * (i+1); }
		public double getMid() { return (getMin()+getMax())*0.5; }
		public int getIndex() { return i; }
		public int getFrequency() { return buckets[i]; }
		
		public boolean contains(double v) {
			return getMin() <= v && v < getMax();
		}
		
		@Override
		public String toString() {
			return String.format("[%f,%f) = %d",getMin(),getMax(),getFrequency());
		}

		@Override
		public int compareTo(Bucket o) {
			return getFrequency() - o.getFrequency();
		}
	}
	
	public Histogram(double minV,double maxV,int nbuckets) {
		this(minV,maxV,(maxV - minV)/nbuckets);
	}
	
	public Histogram(double minV,double maxV,double bWidth) {
		minValue = minV; maxValue = maxV;
		bucketWidth = bWidth;
		double bkts = (maxV - minV) / bWidth;
		int nbuckets = (int) Math.ceil(bkts);
		int nbuckets2 = (int) Math.floor(bkts);
		if ( nbuckets == nbuckets2 )
			++nbuckets;
		buckets = new int[nbuckets];
	}
	
	public int add(ITuple t,int field) {
		double v = t.getDouble(field) - minValue;
		int i = (int)(v / bucketWidth);
		try {
			++buckets[i];
		} catch ( ArrayIndexOutOfBoundsException x ) {
			LOG.error("Invalid index: buckets.length={},i={},V={},v={},minValue={},maxValue={},bucketWidth={}",new Object[] { buckets.length, i, t.getDouble(field), v, minValue,maxValue,bucketWidth });
			throw x;
		}
		return i;
	}

	public void addAll(List<? extends ITuple> data,int field) {
		for ( ITuple t : data ) {
			add(t,field);
		}
	}
	
	public double getBucketMin(int i) { return bucketWidth * i; }
	public double getBucketMax(int i) { return bucketWidth * (i+1); }
	public int    getBucketValue(int i) { return buckets[i]; }
	public double getBucketMid(int i) { return bucketWidth * (i+0.5); }
	public int    getNumBuckets() { return buckets.length; }
	public Bucket getBucket(int i) { return new Bucket(i); }
	public double getDomain() { return maxValue - minValue; }
	public double getBucketWidth() { return bucketWidth; }
	
	public void build(List<ITuple> data,int field) {
		addAll(data,field);
		if ( indexes == null ) {
			indexes = new Bucket[buckets.length];
			for ( int i = 0; i < buckets.length; ++i ) {
				indexes[i] = new Bucket(i);
			}
			Arrays.sort(indexes);
		}
	}
	
	public void build() {
		if ( indexes == null ) {
			indexes = new Bucket[buckets.length];
			for ( int i = 0; i < buckets.length; ++i ) {
				indexes[i] = new Bucket(i);
			}
			Arrays.sort(indexes);
		}
	}
	
	public int getKthHighBucket(int i) {
		return indexes[i].getIndex();
	}
	
	public int getMostFrequentBucket() {
		return indexes[indexes.length-1].getIndex();
	}
	
	public int getMaxFrequency() {
		return indexes[indexes.length-1].getFrequency();
	}
	
	@Override
	public String toString() {
		Bucket[] arr = new Bucket[buckets.length];
		int i = 0;
		for ( Bucket b : this ) {
			arr[i++] = b;
		}
		return Arrays.toString(arr);
	}

	@Override
	public Iterator<Bucket> iterator() {
		return new Iterator<Bucket>() {
			int i;

			@Override
			public boolean hasNext() {
				return i < buckets.length;
			}

			@Override
			public Bucket next() {
				return new Bucket(i++);
			}

			@Override
			public void remove() {
				throw new UnsupportedOperationException();
			}};
	}
}